package com.example.module.utils.exception;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Enumeration;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

abstract public class BaseException extends Exception {
	private static final Logger logger = LogManager.getLogger(BaseException.class);
	private static final long serialVersionUID = 8295273333975229235L;
	private static final ConcurrentHashMap<Class<? extends BaseException>, String> formatMap = new ConcurrentHashMap<>();
	private static final ConcurrentHashMap<String, ResourceBundle> urlResourceBundleMap = new ConcurrentHashMap<>();//no need for using concurrentHashMap
	private static final String DEFAULT_RESOURCE_BUNDLE_KEY = "<:DEFAULT_RESOURCE_BUNDLE:>";
	private static final String RESOURCE_BUNDLE_NAME = "errorDefine";
	static{
		try{
			ResourceBundle rb = ResourceBundle.getBundle(RESOURCE_BUNDLE_NAME);
			urlResourceBundleMap.put(DEFAULT_RESOURCE_BUNDLE_KEY, rb);
		}catch(MissingResourceException e){
			logger.warn("Default \""+RESOURCE_BUNDLE_NAME+"\" not found!");
		}
	}
	private String message;
	abstract public String getErrorCode();
	public BaseException(Object... args) {
		Class<? extends BaseException> clazz = this.getClass();
		String errorCode = getErrorCode();
		String format = formatMap.get(clazz);
		if(format==null){
			synchronized(formatMap){
				format = formatMap.get(clazz);
				if(format==null){
					String resourceBase = getResourceBase(clazz);
					ResourceBundle rb = urlResourceBundleMap.get(resourceBase);
					if(rb == null){
						try{
							rb = ResourceBundle.getBundle(RESOURCE_BUNDLE_NAME, Locale.getDefault(), new ResourceBundleClassLoader(this.getClass()));
						}catch(MissingResourceException e){
							e.printStackTrace();
						}
						urlResourceBundleMap.put(resourceBase, rb);
					}
					try{
						format = rb.getString(errorCode);
					}catch(MissingResourceException e){
						logger.warn("Error Code ["+errorCode+"] undefined in class["+clazz.getName()+"]'s \""+RESOURCE_BUNDLE_NAME+"\" search path.Try get definition from default \""+RESOURCE_BUNDLE_NAME+"\".");
						rb = urlResourceBundleMap.get(DEFAULT_RESOURCE_BUNDLE_KEY);
						if(rb==null){
							logger.error("Default \""+RESOURCE_BUNDLE_NAME+"\" is undefined.");
						}else{
							try{
								format = rb.getString(errorCode);
							}catch(MissingResourceException e1){
								logger.error("Error Code ["+errorCode+"] undefined in default \""+RESOURCE_BUNDLE_NAME+"\".");
							}
						}
					}
					if(format!=null){
						formatMap.put(clazz, format);
					}
				}
			}
		}
		try{
			message = String.format(format,args);
		}catch(NullPointerException e){
			logger.error("Error Code [" + getErrorCode()+"] undefined, Exception Name:"+clazz.getName());
			message = "Error Message for error Code[" + getErrorCode()+"] undefined";
		}
	}
	public BaseException(Throwable t,Object... args){
		this(args);
		initCause(t);
	}
	@Override
	public String getMessage() {
		return message;
	}
	
	private class ResourceBundleClassLoader extends ClassLoader{
		private ClassLoader proxyCl = null;
		private Class<?> clazz = null;
		public ResourceBundleClassLoader(Class<?> clazz) {
			this.proxyCl = clazz.getClassLoader();
			this.clazz = clazz;
		}
		@Override
		public URL getResource(String name) {
			return proxyCl.getResource(name);
		}
		public int hashCode() {
			return proxyCl.hashCode();
		}
		public boolean equals(Object obj) {
			return proxyCl.equals(obj);
		}
		public String toString() {
			return proxyCl.toString();
		}
		public Class<?> loadClass(String name) throws ClassNotFoundException {
			return proxyCl.loadClass(name);
		}
		public Enumeration<URL> getResources(String name) throws IOException {
			return proxyCl.getResources(name);
		}
		public InputStream getResourceAsStream(String name) {
			if(name.endsWith(".properties")){//仅处理properties文件
				try {
					//尝试获取类对应的.class文件位置
					String classBasePath = getResourceBase(clazz);
					//尝试从.class文件位置获取对应配置文件
					logger.trace("Try to get:"+classBasePath+"/"+name);
					URL urlResult = new URL(null, classBasePath+"/"+name, new URLStreamHandler() {//通过指定类的ClassLoader载入URL，设定指定的获取URL连接的方法
						@Override
						protected URLConnection openConnection(URL u) throws IOException {
							URL myUrl = proxyCl.getResource(u.toString());
							if(myUrl==null){//如果指定的ClassLoader不能获得URL，则用默认的方法生成URL
								myUrl = new URL(u.getProtocol(), u.getHost(), u.getPort(), u.getFile());
							}
							return myUrl.openConnection();
						}
					});
					return urlResult.openStream();
				} catch (IOException e) {
				}
			}
			return proxyCl.getResourceAsStream(name);
		}
		public void setDefaultAssertionStatus(boolean enabled) {
			proxyCl.setDefaultAssertionStatus(enabled);
		}
		public void setPackageAssertionStatus(String packageName, boolean enabled) {
			proxyCl.setPackageAssertionStatus(packageName, enabled);
		}
		public void setClassAssertionStatus(String className, boolean enabled) {
			proxyCl.setClassAssertionStatus(className, enabled);
		}
		public void clearAssertionStatus() {
			proxyCl.clearAssertionStatus();
		}
	}
	private static String getResourceBase(Class<?> clazz){
		String classFilePath = clazz.getName().replaceAll("\\.", "/")+".class";
		return clazz.getClassLoader().getResource(classFilePath).toString().replaceAll(Pattern.quote(classFilePath).toString()+"$","").replaceAll("/$", ""); 
	}
}
